import {
  Component,
  Optional,
  Inject,
  ViewEncapsulation,
  Input,
  ElementRef,
  Renderer2,
  ViewChild,
  HostBinding,
  forwardRef,
} from '@angular/core';

import {
  NgModel,
  NG_VALUE_ACCESSOR,
  NG_VALIDATORS,
  NG_ASYNC_VALIDATORS,
} from '@angular/forms';

import { ElementBase } from '../element-base';

@Component({
  selector: 'fui-input-number',
  encapsulation: ViewEncapsulation.None,
  templateUrl: './input-number.component.html',
  styleUrls: ['./input-number.component.sass'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => InputNumberComponent),
    multi: true,
  }],
})
export class InputNumberComponent extends ElementBase<number> {
  @HostBinding('class.fui-input-number') host = true;
  @HostBinding('class.fui-input-number-disabled')
  get disabledClass() {
    return this.disabled;
  }
  @HostBinding('class.fui-input-number-readonly')
  get readonlyClass() {
    return this.readonly;
  }

  @ViewChild('inputElement', { static: true }) inputElement: ElementRef<HTMLInputElement>;
  @ViewChild(NgModel, { static: true }) model: NgModel;

  private el: HTMLElement;
  private precisionStep = 0;
  private precisionFactor = 1;
  private stepValue = 1;

  /** input展示的名字 */
  public displayValue: number | string;

  /** 是否处在输入状态 */
  isComposing = false;

  /** Placeholder. */
  @Input() placeholder = '';

  /** Number min value. */
  @Input() fuiMinValue = -Infinity;

  /** Number max value. */
  @Input() fuiMaxValue = Infinity;

  /** Validation error messages. */
  @Input() errorMsgs: string[] = [];

  /** Incrementing/decrementing step. */
  @Input()
  set step(value: number) {
    this.stepValue = value;
    const stepString = value.toString();
    if (stepString.indexOf('e-') >= 0) {
      this.precisionStep = parseInt(stepString.slice(stepString.indexOf('e-')), 10);
    }
    if (stepString.indexOf('.') >= 0) {
      this.precisionStep = stepString.length - stepString.indexOf('.') - 1;
    }
    this.precisionFactor = Math.pow(10, this.precisionStep);
  }
  get step(): number {
    return this.stepValue;
  }

  constructor(
    @Optional() @Inject(NG_VALIDATORS) validators: Array<any>,
    @Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<any>,
    private elementRef: ElementRef,
    private renderer: Renderer2,
  ) {
    super(validators, asyncValidators);
    this.el = this.elementRef.nativeElement;
  }

  get disabledUp() {
    return (this.value !== undefined) && !((this.value + this.step) <= this.fuiMaxValue);
  }

  get disabledDown() {
    return (this.value !== undefined) && !((this.value - this.step) >= this.fuiMinValue);
  }

  numberUp($event) {
    $event.preventDefault();
    $event.stopPropagation();
    if (this.value === undefined) {
      this.value = this.fuiMinValue || 0;
    }
    if (!this.disabledUp) {
      this.value = this.toPrecisionAsStep(
        (this.precisionFactor * this.value + this.precisionFactor * this.step) / this.precisionFactor,
      );
    }
    this.updateDisplay(this.value);
  }

  numberDown($event) {
    $event.preventDefault();
    $event.stopPropagation();
    if (this.value === undefined) {
      this.value = this.fuiMinValue || 0;
    }
    if (!this.disabledDown) {
      this.value = this.toPrecisionAsStep(
        (this.precisionFactor * this.value - this.precisionFactor * this.step) / this.precisionFactor,
      );
    }
    this.updateDisplay(this.value);
  }

  onModelChange(value: string) {
    this.displayValue = value;
    this.updateModel();
  }

  onInputBlur() {
    this.updateModel();
    this.updateDisplay(this.value);
  }

  private isNotCompleteNumber(num: number | string): boolean {
    return isNaN(num as number) || num === null || !!(num && num.toString().indexOf('.') === num.toString().length - 1);
  }

  private getValidValue(value: number | string): number | string | undefined {
    const val = parseFloat(value as string);
    if (isNaN(val)) {
      return value;
    }
    return val;
  }

  private toPrecisionAsStep(num: number) {
    if (isNaN(num)) {
      return num;
    }
    return Number(Number(num).toFixed(this.precisionStep));
  }

  private updateDisplay(value: number | string) {
    this.inputElement.nativeElement.value = this.displayValue = ((!value && value !== 0) ? null : `${value}`);
  }

  private updateModel() {
    if (this.displayValue && this.displayValue !== this.value) {
      const validValue = this.getValidValue(this.displayValue);
      if (validValue === '' || this.isNotCompleteNumber(validValue)) {
        this.value = null;
      } else {
        this.value = Number(validValue);
      }
    } else {
      this.value = null;
    }
  }

  writeValue(value: number) {
    super.writeValue(value);
    this.updateDisplay(value);
  }
}
